/*
 * Copyright (C) Yumin Wu <wuyuminst@163.com>
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL), available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <dlfcn.h>
#include <linux/videodev2.h>
#include <pthread.h>

#include "rk_aiq_user_api_sysctl.h"
#include "rk_aiq_user_api_imgproc.h"
#include "rk_aiq_user_api_debug.h"

#include "rk_ispv4l2.h"

#define CLEAR(x) memset(&(x), 0, sizeof(x))
#define ERR(...) do { fprintf(stderr, __VA_ARGS__); } while (0)

#define BUFFER_COUNT 8
#define FMT_NUM_PLANES 1

#define IQFILES_PATH "/oem/etc/iqfiles"

struct buffer {
    void *start;
    size_t length;
    int export_fd;
    int sequence;
};

/* video device node */
static char g_vd[32];
static int fd = -1;
/* sensor name */
static char sns_name[32];
/* sensor entity */
static char sns_entity_name[64];
/* v4l2 */
static enum v4l2_buf_type buf_type;
static struct buffer *buffers;
static unsigned int n_buffers;
static int width = 1920;
static int height = 1080;
static int format = V4L2_PIX_FMT_NV12;
/* aiq */
static rk_aiq_sys_ctx_t* aiq_ctx;
/* mainloop */
static pthread_t g_tid;
/* callback */
static ispv4l2_callback g_yuv_cb = NULL;


static void register_ispv4l2_callback(ispv4l2_callback cb)
{
    g_yuv_cb = cb;
}

static void errno_exit(const char *s)
{
    ERR("%s: %s error %d, %s\n", sns_name, s, errno, strerror(errno));
}

static int xioctl(int fh, int request, void *arg)
{
    int r;
    do {
        r = ioctl(fh, request, arg);
    } while (-1 == r && EINTR == errno);
    return r;
}

static int start_capturing(void)
{
    unsigned int i;
    enum v4l2_buf_type type;

    for (i = 0; i < n_buffers; ++i) {
        struct v4l2_buffer buf;

        CLEAR(buf);
        buf.type = buf_type;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == buf_type) {
            struct v4l2_plane planes[FMT_NUM_PLANES];

            buf.m.planes = planes;
            buf.length = FMT_NUM_PLANES;
        }
        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) {
            errno_exit("VIDIOC_QBUF");
            return -1;
        }
    }
    type = buf_type;
    printf("%s:-------- stream on output -------------\n", sns_name);
    if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)) {
        errno_exit("VIDIOC_STREAMON");
        return -1;
    }

    return 0;
}

static void stop_capturing(void)
{
    enum v4l2_buf_type type;

    type = buf_type;
    if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
        errno_exit("VIDIOC_STREAMOFF");
}

static void process_image(int src_fd, const void *p, int sequence, int size)
{
    if(g_yuv_cb) {
        g_yuv_cb(src_fd, p, size, width, height);
    }
}

static void *mainloop(void *arg)
{
    struct v4l2_buffer buf;
    int i, bytesused;

    while (1) {

        CLEAR(buf);

        buf.type = buf_type;
        buf.memory = V4L2_MEMORY_MMAP;

        struct v4l2_plane planes[FMT_NUM_PLANES];
        if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == buf_type) {
            buf.m.planes = planes;
            buf.length = FMT_NUM_PLANES;
        }

        // DQBUF
        if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf))
            errno_exit("VIDIOC_DQBUF");

        i = buf.index;

        if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == buf_type)
            bytesused = buf.m.planes[0].bytesused;
        else
            bytesused = buf.bytesused;
        
        process_image(buffers[i].export_fd, buffers[i].start, buf.sequence, bytesused);

        // QBUF
        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
            errno_exit("VIDIOC_QBUF");
        
        pthread_testcancel();
    }

    printf("%s: rk_ispv4l2 quit\n", __func__);
    pthread_exit(NULL);
}

static int init_mmap(void)
{
    struct v4l2_requestbuffers req;
    int fd_tmp = -1;

    CLEAR(req);

    fd_tmp = fd;

    req.count = BUFFER_COUNT;
    req.type = buf_type;
    req.memory = V4L2_MEMORY_MMAP;

    struct buffer *tmp_buffers = NULL;

    if (-1 == xioctl(fd_tmp, VIDIOC_REQBUFS, &req)) {
        if (EINVAL == errno) {
                ERR("%s: %s does not support "
                            "memory mapping\n" ,sns_name ,g_vd);
                return -1;
        } else {
                errno_exit("VIDIOC_REQBUFS");
                return -1;
        }
    }

    if (req.count < 2) {
        ERR("%s: Insufficient buffer memory on %s\n",sns_name,
                    g_vd);
        return -1;
    }

    tmp_buffers = (struct buffer*)calloc(req.count, sizeof(struct buffer));
    if (!tmp_buffers) {
        ERR("%s: Out of memory\n",sns_name);
        return -1;
    }

    buffers = tmp_buffers;

    for(n_buffers = 0; n_buffers < req.count; ++n_buffers) {
        struct v4l2_buffer buf;
        struct v4l2_plane planes[FMT_NUM_PLANES];
        CLEAR(buf);
        CLEAR(planes);

        buf.type = buf_type;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;

        if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == buf_type) {
            buf.m.planes = planes;
            buf.length = FMT_NUM_PLANES;
        }

        if (-1 == xioctl(fd_tmp, VIDIOC_QUERYBUF, &buf)) {
            errno_exit("VIDIOC_QUERYBUF");
            return -1;
        }
        
        if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == buf_type) {
            tmp_buffers[n_buffers].length = buf.m.planes[0].length;
            tmp_buffers[n_buffers].start =
                mmap(NULL /* start anywhere */,
                        buf.m.planes[0].length,
                        PROT_READ | PROT_WRITE /* required */,
                        MAP_SHARED /* recommended */,
                        fd_tmp, buf.m.planes[0].m.mem_offset);
        } else {
            tmp_buffers[n_buffers].length = buf.length;
            tmp_buffers[n_buffers].start =
                mmap(NULL /* start anywhere */,
                        buf.length,
                        PROT_READ | PROT_WRITE /* required */,
                        MAP_SHARED /* recommended */,
                        fd_tmp, buf.m.offset);
        }

        if (MAP_FAILED == tmp_buffers[n_buffers].start) {
            errno_exit("mmap");
            return -1;
        }

        // export buf dma fd
        struct v4l2_exportbuffer expbuf;
        xcam_mem_clear (expbuf);
        expbuf.type = buf_type;
        expbuf.index = n_buffers;
        expbuf.flags = O_CLOEXEC;
        if (xioctl(fd_tmp, VIDIOC_EXPBUF, &expbuf) < 0) {
            errno_exit("get dma buf failed\n");
            return -1;
        } else {
            printf("%s: get dma buf(%d), fd = %d\n", 
                sns_name, n_buffers, expbuf.fd);
        }
        tmp_buffers[n_buffers].export_fd = expbuf.fd;
    }

    return 0;
}

static int v4l2_init_device(void)
{
    struct v4l2_capability cap;
    struct v4l2_format fmt;

    if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) {
        if (EINVAL == errno) {
            ERR("%s: %s is no V4L2 device\n", sns_name,
                        g_vd);
            return -1;
        } else {
            errno_exit("VIDIOC_QUERYCAP");
            return -1;
        }
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) &&
            !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE)) {
        ERR("%s: %s is not a video capture device, capabilities: %x\n",
                        sns_name, g_vd, cap.capabilities);
        return -1;
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        ERR("%s: %s does not support streaming i/o\n", sns_name, g_vd);
        return -1;
    }

    if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
        buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    } else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) {
        buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    }
    CLEAR(fmt);
    fmt.type = buf_type;
    fmt.fmt.pix.width = width;
    fmt.fmt.pix.height = height;
    fmt.fmt.pix.pixelformat = format;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    fmt.fmt.pix.quantization = V4L2_QUANTIZATION_FULL_RANGE;

    if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) {
        errno_exit("VIDIOC_S_FMT");
        return -1;
    }

    if(init_mmap())
        return -1;

    /* stream on */
    if(start_capturing())
        return -1;
}

static void rkaiq_init(void)
{
    strcpy(sns_entity_name, rk_aiq_uapi_sysctl_getBindedSnsEntNmByVd(g_vd));
    sscanf(&sns_entity_name[6], "%s", sns_name);

    printf("\n==================================\n");
    printf("sns_entity_name:%s\n", sns_entity_name);
    printf("sns_name:%s\n", sns_name);
    printf("==================================\n\n");
}

static int rkaiq_process(void)
{
    aiq_ctx = rk_aiq_uapi_sysctl_init(sns_entity_name, IQFILES_PATH, NULL, NULL);
    if(aiq_ctx)
    {
        printf("%s:-------- init mipi tx/rx -------------\n",sns_name);
        rk_aiq_uapi_debug_captureRawYuvSync(aiq_ctx, CAPTURE_RAW_AND_YUV_SYNC);
        XCamReturn ret = rk_aiq_uapi_sysctl_prepare(aiq_ctx, width, height, 0);
        if (ret != XCAM_RETURN_NO_ERROR) {
            printf("%s: rk_aiq_uapi_sysctl_prepare failed!\n", __func__);
            return -1;
        }
        ret = rk_aiq_uapi_sysctl_start(aiq_ctx);
        if (ret != XCAM_RETURN_NO_ERROR) {
            printf("%s: rk_aiq_uapi_sysctl_start failed!\n", __func__);
            return -1;
        }
    } else {
        printf("%s: aiq_ctx is NULL!\n", __func__);
        return -1;
    }

    return 0;
}

static int rkisp_routine(void)
{
    int ret;
    rkaiq_init();

    printf("%s:-------- open output dev -------------\n",sns_name);
    fd = open(g_vd, O_RDWR /* required */ /*| O_NONBLOCK*/, 0);
    if (-1 == fd) {
        printf("%s: open device error!\n", __func__);
        return -1;
    }

    ret = rkaiq_process();
    if(ret)
        return -1;

    return 0;
}

int rk_ispv4l2_init(char* dev_node, ispv4l2_callback cb, int w, int h)
{
    int ret;
    if(!dev_node) {
        printf("%s: dev_node is NULL!\n", __func__);
        return -1;
    }
    sscanf(dev_node, "%s", g_vd);

    if(w)
        width = w;

    if(h)
        height = h;

    register_ispv4l2_callback(cb);

	ret = rkisp_routine();
    if(ret) {
        return -1;
    }

	ret = v4l2_init_device();
    if(ret) {
        return -1;
    }

    if (pthread_create(&g_tid, NULL, mainloop, NULL)) {
        printf("%s: mainloop start failed\n", __func__);
        return -1;
    }

    return 0;
}

void rk_ispv4l2_exit()
{
    unsigned int i;

    /* mainloop exit */
    if (g_tid) {
        pthread_cancel(g_tid);
        pthread_join(g_tid, NULL);
        g_tid = 0;
    }

    /* stop aiq */
    if (aiq_ctx) {
        printf("%s:-------- stop aiq -------------\n", sns_name);
		rk_aiq_uapi_sysctl_stop(aiq_ctx, false);
	}
    /* stream off */
    stop_capturing();

    /* deinit aiq */
    if (aiq_ctx) {
        printf("%s:-------- deinit aiq -------------\n", sns_name);
		rk_aiq_uapi_sysctl_deinit(aiq_ctx);
        printf("%s:-------- deinit aiq end -------------\n", sns_name);
    }

    /* release buffers */
    for (i = 0; i < n_buffers; ++i) {
        if (-1 == munmap(buffers[i].start, buffers[i].length))
            errno_exit("munmap");
        close(buffers[i].export_fd);
    }
    free(buffers);

    /* close device */
    if (-1 == close(fd))
        errno_exit("close");
    fd = -1;
}